Skip to content

feat: bounded message window for centered Jump to Message#7388

Open
diegolmello wants to merge 11 commits into
developfrom
oasis-ocean
Open

feat: bounded message window for centered Jump to Message#7388
diegolmello wants to merge 11 commits into
developfrom
oasis-ocean

Conversation

@diegolmello

@diegolmello diegolmello commented Jun 8, 2026

Copy link
Copy Markdown
Member

Proposed changes

Rebuilds Jump to Message on a bounded message window so a deep jump re-anchors in one step (O(1)) instead of growing the window page-by-page (O(pages)) — removing the root cause of the old 5s-race flakiness — without migrating off the custom native inverted list.

The whole capability is one optional upper ts bound (highTs) on the existing WatermelonDB observation:

  • highTs == nullLive Window (newest-first, follows the Live Tail) — unchanged default behavior.
  • finite highTsAnchored Window pinned around the target, below the Live Tail.

A pure, DB/React-free anchor resolver decides the bound (anchorForTarget) and climbs it back toward live as Newer Loaders are consumed (raiseOrRelease), releasing to a Live Window only once the Gap to the Live Tail has fully closed (invariant: never release across an open Gap). "Rejoin live" is now explicit — the Load Newer chain or the jump-to-bottom FAB (which stays visible while anchored).

Also included:

  • Reading-position preservation on release — on RELEASE, grow the Live Window by the count of cached messages above the old bound so a deep target isn't evicted by take(count) and the list doesn't snap to the Live Tail.
  • Scroll-to-index recursion guard — defer one frame + cap retries for onScrollToIndexFailed (no getItemLayout → the inverted list re-fires it synchronously → stack overflow). The retry re-applies the same centering + header-clearing view offset as the initial scroll, so a distant jump that falls back to the retry path still lands centered instead of hidden behind the room header.
  • Bounded window growth for deep anchored targets — when a target sits below the anchored window's first page (a large Chunk or filtered rows between the bound and the target), the jump grows the window one page at a time (capped) to pull the target into the rendered rows, instead of waiting out the safety timeout and aborting.
  • Jump orchestration — gate anchoring on window membership (isMessageInWindow), not message origin, so a locally-cached-but-out-of-window target no longer silently aborts; three roads (in-window / cached-out-of-window / server) plus a target-ts fallback.
  • Supporting fixes: keep the live socket on deeplink login; tolerate an empty 2FA code on the DDP loadSurroundingMessages call; one-shot jump param so re-searching the same message re-fires; guard the anchor raise/release against a re-subscribe landing mid-flight (room switch / concurrent raise) so a stale read can't corrupt the new window.

Design rationale and the rejected alternative (FlashList v2 migration) are in the ADR at app/views/RoomView/docs/adr/0001-bounded-window-over-flashlist.md. New domain vocabulary is in UBIQUITOUS_LANGUAGE.md (§ Message Loading).

Issue(s)

https://rocketchat.atlassian.net/browse/NATIVE-1224

How to test or reproduce

  1. Open a room with a long history (hundreds of messages, with gaps).
  2. Search for / tap a target far from the Live Tail. It should re-anchor and center on the target in one step — no long page-by-page scroll, no abort — and land clear of the room header (not tucked behind it), even for very deep targets that fall back to the scroll retry path.
  3. Climb back via "Load newer" / scrolling up; the window rejoins live only when the gap closes; the jump-to-bottom FAB is available throughout.
  4. Jump to a deep target, then keep loading newer until release — the reading position is preserved (no snap to the Live Tail).
  5. Jump to the same message twice in a row via Search — it re-fires both times.
  6. Jump to a quoted reply pointing at an on-screen message — it scrolls in place, leaving the Live Tail intact.

Screenshots

No static UI redesign; changes are behavioral (centered jump; FAB visibility while anchored).

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

The list engine is unchanged; a future FlashList migration remains possible but is neither required nor blocked by this work. Ordering stays ts-only (a ts + _id composite is a deferred follow-up noted in the ADR). New unit coverage: anchorResolver, useMessages (release-window growth), useScroll (deferred + capped retry, header-clearing offset on retry, bounded growth + growth cap for deep anchored targets), getLocalAnchor.

Summary by CodeRabbit

  • New Features

    • Jump-to-Message now anchors the message window to load surrounding context for out-of-window targets; anchored jumps retry boundedly and clear when complete. Jump-to-bottom explicitly releases anchors. FAB (jump UI) stays visible for anchored views.
  • Documentation

    • Added ADR describing the bounded message-window approach.
  • Tests

    • Expanded test coverage for anchoring, jump behavior, scrolling, and windowing logic.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR implements anchored, upper-bounded message windows (highTs) for Jump-to-Message: domain terminology and ADR, anchor resolution helpers, local anchor service, message-query bounding and rejoin logic in useMessages, a pending-jump scroll state machine in useScroll, list wiring and UI adjustments for anchored views, and RoomView orchestration to compute and use anchors.

Changes

Bounded Message Window for Jump-to-Message

Layer / File(s) Summary
Domain terminology and anchor resolution contracts
UBIQUITOUS_LANGUAGE.md, app/views/RoomView/List/hooks/anchorResolver.ts, app/views/RoomView/List/hooks/anchorResolver.test.ts, app/views/RoomView/docs/adr/0001-bounded-window-over-flashlist.md
Adds Message Loading terminology (Message Window, Live Tail, Anchored Window, Chunks, Loader Rows, Jump), clarifies ambiguous terms, and implements anchorForTarget and raiseOrRelease with tests and ADR documenting the bounded-window approach.
Message info extensions and local anchor service
app/views/RoomView/services/getMessageInfo.ts, app/views/RoomView/services/getLocalAnchor.ts, app/views/RoomView/services/getLocalAnchor.test.ts, app/views/RoomView/services/index.ts
Adds ts to message payloads, implements getLocalAnchorTs to find nearest newer-loader above a target from local DB, tests its behavior, and exports the service.
List interfaces and anchored navigation
app/views/RoomView/List/definitions.ts, app/views/RoomView/List/components/List.tsx
Adds isAnchored?: boolean to IListProps, extends IListContainerRef.jumpToMessage to accept optional highTs, adds isMessageInWindow, and makes FAB visibility dependent on anchored state.
Message observation with anchored bounds and rejoin transitions
app/views/RoomView/List/hooks/useMessages.ts, app/views/RoomView/List/hooks/useMessages.test.tsx
Implements optional ts <= highTs query bounds for anchored windows, tracks boundary-loader presence, detects present→absent transitions and performs targeted one-shot reads to decide raise-or-release (or increase take on release), resets pagination on anchor changes, and exposes { highTs, setHighTs } with tests validating bound clauses and transitions.
Jump state machine and scroll retry orchestration
app/views/RoomView/List/hooks/useScroll.ts, app/views/RoomView/List/hooks/useScroll.test.tsx
Refactors useScroll into a single in-flight pendingJump model with safety timeouts, useLayoutEffect-driven completion when target appears, bounded anchored growth via fetchMessages, deferred bounded retries for scrollToIndex failures that recompute target index and preserve offsets, anchor-release on jumpToBottom, and extensive tests covering anchored/non-anchored flows, retries, and safety aborts.
List component and container wiring for anchored mode
app/views/RoomView/List/index.tsx, app/views/RoomView/List/components/List.tsx
Threads highTs/setHighTs into useScroll, exposes isMessageInWindow on the list ref, passes isAnchored={highTs != null} to List, and keeps FAB visible while anchored.
RoomView jump orchestration with anchoring
app/views/RoomView/index.tsx
Coordinates Jump-to-Message: checks in-window membership, computes highTs using anchorForTarget (server) or getLocalAnchorTs (local), falls back to message ts if needed, synchronizes Fabric then calls ListContainer.jumpToMessage(message.id, highTs), and clears one-shot jumpToMessageId navigation params after handling.

SDK TOTP Argument Handling

Layer / File(s) Summary
Conditional TOTP argument in SDK method calls
app/lib/services/sdk.ts
Updates Sdk.methodCall to append the TOTP positional argument only when present using a conditional spread, avoiding unconditional empty-string trailing args that can break method signatures.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: bounded message window for centered Jump to Message' accurately summarizes the main feature: implementing a bounded message window to support Jump to Message functionality with centered positioning.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • NATIVE-1224: Request failed with status code 401

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@diegolmello diegolmello changed the title feat(RoomView): bounded message window for centered Jump to Message feat: bounded message window for centered Jump to Message Jun 8, 2026
diegolmello and others added 9 commits June 8, 2026 17:00
…calls

sdk.methodCall appended `this.code || ''` (the TOTP code) as a trailing
positional argument to every DDP method call. That empty string was a
harmless ignored extra until the server's loadSurroundingMessages method
grew a typed `showThreadMessages: boolean` 3rd param (RocketChat/Rocket.Chat
#39092). The '' then fails the server's `check(showThreadMessages, Boolean)`,
returning a 500 that rejects loadSurroundingMessages and aborts the whole
jump-to-message flow (loading shows, no navigation).

Append the TOTP code only when a 2FA flow is in progress, so non-2FA DDP
calls send exactly their declared params — matching the REST path and the
server default. The 2FA flow is preserved: the codeless first call still
gets totp-required, then the retry appends the resolved code.

Covered by the existing .maestro/tests/room/jump-to-message.yaml flow.
… guard scroll-to-index retry

NATIVE-1229 bounded-window jump validation checkpoint.

- useMessages: on Anchored Window RELEASE, grow the Live Window by the count of
  cached messages above the old bound (count += aboveCount) so a deep jump target
  is not evicted by take(count) and the view no longer snaps to the Live Tail.
- useScroll: defer one frame + cap the onScrollToIndexFailed retry to break the
  synchronous VirtualizedList re-fire recursion (stack overflow) on an unmeasurable
  target; re-read the target at fire time so a stale retry cannot scroll wrong.
- getLocalAnchor / getMessageInfo / services: anchored-jump re-seed wiring.
- Regression tests: useMessages (release window growth), useScroll (deferred+capped
  retry), getLocalAnchor.

Includes temporary [JUMP-DBG] instrumentation; to be stripped before merge.
…ntation

Strip the in-runtime debug buffer, DB-primitive globals, and the deterministic
jump/climb drivers used to validate the bounded-window jump on-device. The real
fixes (anchored-window release growth, scroll-to-index retry guard,
isMessageInWindow) are unaffected.
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.74.0.109044

…etry

The onScrollToIndexFailed retry scrolled with only { index, animated: false },
omitting the viewPosition/viewOffset the two primary jump scrolls use to center
the target and clear the sticky room header. On the inverted list this parked the
(already-highlighted) target flush at the top edge, hidden behind the header.

It only surfaced "sometimes" because the retry fires solely when the first
scrollToIndex cannot measure the target's frame yet — distant/mid-window jumps
with no getItemLayout. Near/contiguous jumps took the correctly-offset primary
path.

Consolidate the landing position into one shared JUMP_SCROLL_POSITION constant
applied at all three scrollToIndex sites (initial scroll + retry) so they can no
longer drift apart. Regression test asserts the retry carries the header-clearing
offset.
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.74.0.109049

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

…hor lifecycle

Address code review findings on the bounded-window jump:
- Jump completion now grows the Message Window by one page (bounded) when an
  anchored target sits deeper than the first page, instead of silently waiting
  for the safety-net abort to fire. Capped at MAX_JUMP_GROWTH_RETRIES.
- Guard raiseOrReleaseAnchor against a re-subscribe landing during its awaits
  (subscription-identity check), so a stale invocation cannot corrupt the new
  window's count/highTs after a room switch or another raise.
- Drop the dead scrollFailRetries reset at the cap so the onScrollToIndexFailed
  retry budget is truly per-jump (reset only at jump start).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
app/views/RoomView/List/hooks/useScroll.test.tsx (1)

19-38: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Mock fetchMessages with the same async contract as production.

useScroll expects fetchMessages: () => Promise<void>, but this helper injects a plain jest.fn() that returns undefined. That means this suite won't catch promise-handling regressions in the new growth path. Make the default mock resolve a promise so the tests exercise the real contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/RoomView/List/hooks/useScroll.test.tsx` around lines 19 - 38, The
test helper renderUseScroll currently injects fetchMessages as jest.fn() which
returns undefined; update the default mock to match the production contract (a
function returning a Promise<void>) so tests exercise async behavior—e.g. change
the default fetchMessages parameter in renderUseScroll to an async-resolving
mock (use fetchMessages = jest.fn().mockResolvedValue(undefined) or an async ()
=> Promise.resolve()) and ensure useScroll is invoked with that mock; keep
references to renderUseScroll, useScroll, makeListRef and makeMessagesIdsRef so
the helper remains otherwise unchanged.
app/views/RoomView/List/hooks/useScroll.ts (2)

72-80: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve the in-flight jump during unmount cleanup.

This cleanup clears pendingJump.current?.safety, but it never settles the promise returned by jumpToMessage(). If the list unmounts mid-jump (for example during a room switch), the only remaining resolution path is removed here and any caller awaiting that jump stays pending indefinitely.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/RoomView/List/hooks/useScroll.ts` around lines 72 - 80, The cleanup
in useEffect currently clears highlightTimeout.current and
pendingJump.current.safety but doesn't settle the promise created by
jumpToMessage(), leaving callers awaiting indefinitely; update the unmount
cleanup in useScroll.ts to, after clearing pendingJump.current.safety, also call
the stored completion callback on pendingJump.current (e.g.,
pendingJump.current.reject or pendingJump.current.resolve) with a
cancellation/error value so the jumpToMessage promise is settled, then null out
pendingJump.current to avoid leaks; reference pendingJump, jumpToMessage,
highlightTimeout and the useEffect cleanup when making the change.

176-182: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bind deferred scroll retries to the jump that scheduled them.

This timeout keeps params.highestMeasuredFrameIndex from the old failure, but it re-reads the latest target id at fire time. If another jump starts before the timer runs and its target is still absent, targetIndex stays -1 and this falls back to the previous jump's measured index, which can scroll the new window to the wrong row. Track/cancel the retry timer per jump before issuing the fallback scroll.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/RoomView/List/hooks/useScroll.ts` around lines 176 - 182, The
deferred scroll retry must be bound to the specific jump that scheduled it: when
scheduling the setTimeout inside useScroll, capture the current jump identifier
(e.g., pendingJump.current?.messageId or an internal jumpId) and store the timer
id on that jump (e.g., pendingJump.current.retryTimer or a map keyed by jumpId);
when a new jump starts clear any existing retry timer for the previous jump;
inside the timeout callback verify the captured jumpId still matches the active
jump (e.g., pendingJump.current?.messageId === capturedJumpId) before falling
back to params.highestMeasuredFrameIndex and calling
listRef.current.scrollToIndex, and clear the stored retryTimer on success or
cancellation so retries don’t apply to later jumps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/views/RoomView/List/hooks/useScroll.ts`:
- Around line 149-154: The code calls fetchMessages() fire-and-forget inside the
anchored-jump growth branch which can produce unhandled promise rejections; wrap
the fetchMessages() call with explicit error handling (e.g.
fetchMessages().catch(err => { /* surface error and abort the jump */ })) and on
error either abort the jump (use the existing jump state handler or call an
abort function) or set a visible failure state so the safety timeout doesn't
silently wait; ensure you reference and update jumpGrowthRetries.current and
MAX_JUMP_GROWTH_RETRIES logic consistently when handling the error so
retries/aborts remain correct.

---

Outside diff comments:
In `@app/views/RoomView/List/hooks/useScroll.test.tsx`:
- Around line 19-38: The test helper renderUseScroll currently injects
fetchMessages as jest.fn() which returns undefined; update the default mock to
match the production contract (a function returning a Promise<void>) so tests
exercise async behavior—e.g. change the default fetchMessages parameter in
renderUseScroll to an async-resolving mock (use fetchMessages =
jest.fn().mockResolvedValue(undefined) or an async () => Promise.resolve()) and
ensure useScroll is invoked with that mock; keep references to renderUseScroll,
useScroll, makeListRef and makeMessagesIdsRef so the helper remains otherwise
unchanged.

In `@app/views/RoomView/List/hooks/useScroll.ts`:
- Around line 72-80: The cleanup in useEffect currently clears
highlightTimeout.current and pendingJump.current.safety but doesn't settle the
promise created by jumpToMessage(), leaving callers awaiting indefinitely;
update the unmount cleanup in useScroll.ts to, after clearing
pendingJump.current.safety, also call the stored completion callback on
pendingJump.current (e.g., pendingJump.current.reject or
pendingJump.current.resolve) with a cancellation/error value so the
jumpToMessage promise is settled, then null out pendingJump.current to avoid
leaks; reference pendingJump, jumpToMessage, highlightTimeout and the useEffect
cleanup when making the change.
- Around line 176-182: The deferred scroll retry must be bound to the specific
jump that scheduled it: when scheduling the setTimeout inside useScroll, capture
the current jump identifier (e.g., pendingJump.current?.messageId or an internal
jumpId) and store the timer id on that jump (e.g.,
pendingJump.current.retryTimer or a map keyed by jumpId); when a new jump starts
clear any existing retry timer for the previous jump; inside the timeout
callback verify the captured jumpId still matches the active jump (e.g.,
pendingJump.current?.messageId === capturedJumpId) before falling back to
params.highestMeasuredFrameIndex and calling listRef.current.scrollToIndex, and
clear the stored retryTimer on success or cancellation so retries don’t apply to
later jumps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 436f179d-7006-45ba-b668-0297ccf794b2

📥 Commits

Reviewing files that changed from the base of the PR and between 090bc41 and ad23b56.

📒 Files selected for processing (4)
  • app/views/RoomView/List/hooks/useMessages.ts
  • app/views/RoomView/List/hooks/useScroll.test.tsx
  • app/views/RoomView/List/hooks/useScroll.ts
  • app/views/RoomView/List/index.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/views/RoomView/List/index.tsx
  • app/views/RoomView/List/hooks/useMessages.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/views/RoomView/List/hooks/useScroll.ts
  • app/views/RoomView/List/hooks/useScroll.test.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Use TypeScript with strict mode enabled

Files:

  • app/views/RoomView/List/hooks/useScroll.ts
  • app/views/RoomView/List/hooks/useScroll.test.tsx
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier formatting with tabs, single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses

Files:

  • app/views/RoomView/List/hooks/useScroll.ts
  • app/views/RoomView/List/hooks/useScroll.test.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enforce ESLint rules from @rocket.chat/eslint-config with React, React Native, TypeScript, and Jest plugins

Files:

  • app/views/RoomView/List/hooks/useScroll.ts
  • app/views/RoomView/List/hooks/useScroll.test.tsx
app/views/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place screen components in 'app/views/' directory

Files:

  • app/views/RoomView/List/hooks/useScroll.ts
  • app/views/RoomView/List/hooks/useScroll.test.tsx
🧠 Learnings (1)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/views/RoomView/List/hooks/useScroll.ts
  • app/views/RoomView/List/hooks/useScroll.test.tsx

Comment on lines +149 to +154
// Anchored target deeper than the current window: grow by one page (bounded) to pull it in.
// The safety net still aborts if it never materialises after the cap.
if (jump.anchored && jumpGrowthRetries.current < MAX_JUMP_GROWTH_RETRIES) {
jumpGrowthRetries.current += 1;
fetchMessages();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle window-growth fetch failures explicitly.

fetchMessages() is fire-and-forget here. If it rejects, this creates an unhandled rejection and the jump just sits until the five-second safety timeout. Please attach explicit error handling here and either abort the jump or surface the failure immediately.

Based on learnings, this repo avoids floating promises without explicit .catch(...); as per coding guidelines, async operations should use explicit error handling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/RoomView/List/hooks/useScroll.ts` around lines 149 - 154, The code
calls fetchMessages() fire-and-forget inside the anchored-jump growth branch
which can produce unhandled promise rejections; wrap the fetchMessages() call
with explicit error handling (e.g. fetchMessages().catch(err => { /* surface
error and abort the jump */ })) and on error either abort the jump (use the
existing jump state handler or call an abort function) or set a visible failure
state so the safety timeout doesn't silently wait; ensure you reference and
update jumpGrowthRetries.current and MAX_JUMP_GROWTH_RETRIES logic consistently
when handling the error so retries/aborts remain correct.

Sources: Coding guidelines, Learnings

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.74.0.109053

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant